/*
 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as ts from "@koalaui/ets-tsc"
import * as fs from "fs"
import * as path from "path"
import { Importer } from './Importer'
import { EtsIdentifierResolver } from "./EtsIdentifierResolver"
import { StructTransformer } from './StructTransformer'
import { StyleTransformer, EtsFirstArgTransformer } from './StyleTransformer'
import { NameCollector } from './NameCollector'
import { CallTable, IssueTable, NameTable } from './utils'
import { LazyTransformer } from './LazyTransformer'
import { EntryTracker } from './EntryTracker'
import { filterOutDiagnostics } from './DiagnosticFilter'
import { DollarTransformer, ApplicationInfo } from './DollarTransformer'
import { ExtensionStylesTransformer } from './ExtensionStylesTransformer'
import { NewTransformer } from './NewTransformer'
import { CustomBuilderTransformer } from './CustomBuilderTransformer'
import { CallTransformer } from './CallTransformer'
import { DebugVisitor } from './AbstractVisitor'
import { LegacyCallTransformer } from './LegacyCallTransformer'
import { AbilityTransformer } from './AbilityTransformer'
import { DollarDollarTransformer } from "./DollarDollarTransformer"
import { ObservedResolver } from "./ObservedResolver"

interface ArkToKoOptions {
    trace?: boolean
    source?: string
    destination?: string
    arkui?: string
    moduleInfo?: (moduleName: string) => any,
    applicationInfo?: ApplicationInfo
}

function printSourceFile(sourceFile: ts.Node, source?: string, destination?: string, extension?: string) {
    if (!ts.isSourceFile(sourceFile)) throw new Error("Expected SourceFile, got: " + ts.SyntaxKind[sourceFile.kind])
    if (!destination) console.log("OUTPUT")

    const printer = ts.createPrinter({ removeComments: false })
    const text = printer.printNode(ts.EmitHint.SourceFile, sourceFile, sourceFile)
    if (destination) {
        const absoluteDestination = path.resolve(destination)
        const absoluteSource = source ? path.resolve(source) : path.resolve()
        const fileBaseName = path.basename(sourceFile.fileName, extension ?? ".ets")
        const fileDirName = path.dirname(sourceFile.fileName)
        const fileRelativeName = path.relative(absoluteSource, fileDirName)
        const dir = path.join(absoluteDestination, fileRelativeName)
        const file = path.join(dir, fileBaseName + ".ts")
        console.log("KOALA: " + path.normalize(file))
        fs.mkdirSync(dir, { recursive: true })
        fs.writeFileSync(file, text, 'utf8')
    } else {
        console.log(text)
    }
}

function updateRouterDestinationsFile(destination: string|undefined, entryTracker: EntryTracker) {
    const entries = Array.from(entryTracker.entries.entries())
    const comment = "// This file has been autogenerated. Do not update manually."
    const imports: string[] = []
    const uses: string[] = []
    entries.forEach((entry, i) => {
        const [file, component] = entry
        const alias = "Entry" + i
        imports.push(`import { ${component} as ${alias} } from \"./${file}\"`)
        uses.push(`\tconst __force${alias}Use = ${alias}`)
    })

    const registerFunction =`
export function registerRoutes() {
${uses.join("\n")}
}
`
    const text = comment + "\n" +
        imports.join("\n") + "\n" +
        registerFunction

    fs.writeFileSync(path.join(destination ?? ".", "__router_initialization.ts"), text)
}

function fileIsEligible(fileName: string): boolean {
    return path.extname(fileName) == ".ets"
}

function prepareDestination(destination: string | undefined) {
    if (destination) {
        fs.mkdirSync(destination, { recursive: true })
    }
}

export function arkExpandFile(
    sourceFile: ts.SourceFile,
    arkuiPackage: string,
    typeChecker: ts.TypeChecker,
    ctx: ts.TransformationContext,
    extras: ts.TransformerExtras | undefined,
    entryTracker: EntryTracker,
    moduleInfo?: (moduleName: string) => any,
    applicationInfo?: ApplicationInfo
): ts.SourceFile {
    const importer = new Importer(arkuiPackage, moduleInfo)
    const nameTable = new NameTable()
    const issueTable = new IssueTable()
    const callTable = new CallTable()

    /* Uncomment to dump AST on entry */

    // const debug = new DebugVisitor(sourceFile, ctx)
    // debug.visitor(sourceFile)

        // resolve identifiers automatically imported from index-full.d.ts
    new EtsIdentifierResolver(sourceFile, ctx, typeChecker, importer)
        .visitor(sourceFile)

    const dollarDollarTransformer = new DollarDollarTransformer(sourceFile, ctx)
    const callTransformer = new CallTransformer(sourceFile, ctx, typeChecker, callTable, importer)
    const lazyTransformer = new LazyTransformer(sourceFile, ctx, importer, callTable)
    const newTransformer = new NewTransformer(sourceFile, ctx, importer, issueTable)
    const customBuilderTransformer = new CustomBuilderTransformer(sourceFile, ctx, typeChecker, callTable)
    const legacyCallTransformer = new LegacyCallTransformer(sourceFile, ctx, importer, callTable)
    const extensionStylesTransformer = new ExtensionStylesTransformer(sourceFile, ctx, typeChecker, importer)
    const preprocessorTransformer = new EtsFirstArgTransformer(sourceFile, ctx, importer, callTable)
    const styleTransformer = new StyleTransformer(sourceFile, ctx, typeChecker, importer, extensionStylesTransformer, callTable)
    const structTransformer = new StructTransformer(sourceFile, ctx, typeChecker, importer, nameTable, entryTracker, callTable, extras)
    const nameCollector = new NameCollector(sourceFile, ctx, nameTable, issueTable)
    const dollarTransformer = new DollarTransformer(sourceFile, ctx, nameTable, importer, applicationInfo)
    const abilityTransformer = new AbilityTransformer(sourceFile, ctx, importer)
    const observedResolver = new ObservedResolver(sourceFile, ctx, importer)

    nameCollector.visitor(sourceFile)

    const translatedDollarDollar = dollarDollarTransformer.visitor(sourceFile) as ts.SourceFile
    const translatedCalls = callTransformer.visitor(translatedDollarDollar) as ts.SourceFile
    const translatedDollar = dollarTransformer.visitor(translatedCalls) as ts.SourceFile
    const translatedLazy = lazyTransformer.visitor(translatedDollar) as ts.SourceFile
    const translatedNew = newTransformer.visitor(translatedLazy) as ts.SourceFile
    const translatedCustomBuilders = customBuilderTransformer.visitor(translatedNew) as ts.SourceFile
    const translatedLegacyCalls = legacyCallTransformer.visitor(translatedCustomBuilders) as ts.SourceFile
    const translatedStyleFunctions = extensionStylesTransformer.visitor(translatedLegacyCalls) as ts.SourceFile
    const preprocessedEts = preprocessorTransformer.visitor(translatedStyleFunctions) as ts.SourceFile
    const translatedStyle = styleTransformer.visitor(preprocessedEts) as ts.SourceFile
    const translatedStruct = structTransformer.visitor(translatedStyle) as ts.SourceFile
    const translatedAbility = abilityTransformer.visitor(translatedStruct) as ts.SourceFile
    const resolvedObserved = observedResolver.visitor(translatedAbility) as ts.SourceFile

    const finalStage = resolvedObserved

    filterOutDiagnostics(sourceFile, nameTable, issueTable, extras)

    return ts.factory.updateSourceFile(
        finalStage,
        [
            ...importer.generate(),
            ...finalStage.statements,
            // TODO: do we need a better way to obtain it?
            // ...structTranslator.entryCode
        ],
        finalStage.isDeclarationFile,
        finalStage.referencedFiles,
        finalStage.typeReferenceDirectives,
        finalStage.hasNoDefaultLib,
        finalStage.libReferenceDirectives
    )
}

export default function arkExpander(program: ts.Program, userPluginOptions: ArkToKoOptions, extras: ts.TransformerExtras) {
    const pluginOptions = {
        trace: userPluginOptions.trace ?? true,
        source: userPluginOptions.source ?? ".",
        destination: userPluginOptions.destination ?? "../generated",
        arkui: userPluginOptions.arkui ?? "@koalaui/koala-arkui",
        moduleInfo: userPluginOptions.moduleInfo,
        applicationInfo: userPluginOptions.applicationInfo
    }
    const typeChecker = program.getTypeChecker()
    prepareDestination(pluginOptions.destination)
    const entryTracker = new EntryTracker(pluginOptions.source)

    return (ctx: ts.TransformationContext) => {
        return (sourceFile: ts.SourceFile) => {

            if (!fileIsEligible(sourceFile.fileName)) {
                console.log("Verbatim TS: ", sourceFile.fileName)
                printSourceFile(sourceFile, pluginOptions.source, pluginOptions.destination, ".ts")
                return sourceFile
            } else {
                console.log("ETS->TS: " + path.normalize(sourceFile.fileName))
            }

            let final = arkExpandFile(sourceFile, pluginOptions.arkui, typeChecker, ctx, extras, entryTracker, pluginOptions.moduleInfo, pluginOptions.applicationInfo)

            printSourceFile(final, pluginOptions.source, pluginOptions.destination)
            if (pluginOptions.arkui != "@koalaui/arkts-arkui") {
                updateRouterDestinationsFile(pluginOptions.destination, entryTracker)
            }

            // if (structTranslator.entryFile) {
            //     emitStartupFile(structTranslator.entryFile, pluginOptions.source!, pluginOptions.destination!)
            // }

            return final
        }
    }
}

export function makeEtsExpander(typeChecker: ts.TypeChecker, userPluginOptions: ArkToKoOptions, extras: ts.TransformerExtras) {
    const pluginOptions = {
        trace: userPluginOptions.trace ?? true,
        source: userPluginOptions.source ?? ".",
        destination: userPluginOptions.destination ?? "../generated",
        arkui: userPluginOptions.arkui ?? "@koalaui/koala-arkui",
        moduleInfo: userPluginOptions.moduleInfo
    }
    const entryTracker = new EntryTracker(pluginOptions.source)

    return (ctx: ts.TransformationContext) => {
        return (sourceFile: ts.SourceFile) => {
            return arkExpandFile(sourceFile, pluginOptions.arkui, typeChecker, ctx, extras, entryTracker)
        }
    }
}
